🤖 feat: wire aggregated API server to codersdk backend#50
Conversation
|
@codex review |
2f64bcb to
71d4395
Compare
|
@codex review Fixed inline HTML in generated API reference docs (multi-line Go comments were producing |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 0338b163c2
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
@codex review Addressed all 3 review comments:
All 3 threads resolved. Tests added for each fix. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 3ddea6194d
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
@codex review Fixed: |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 69da96417d
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
@codex review Addressed both P1 comments:
All threads resolved. Tests added. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 6c51a37ad8
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
@codex review Addressed both comments:
All threads resolved. Tests added. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: d16f789683
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
@codex review Addressed both comments:
All threads resolved. Tests added. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 52f5a24da3
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
@codex review Addressed both comments:
All threads resolved. Tests added. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 8553d7edc3
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
@codex review Addressed both comments:
All threads resolved. Tests added. |
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: f96424b0ac
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 479d3a343e
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: c2e50b4983
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: d803aed6db
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 81f998dcc1
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 2400a7dc35
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
@codex review |
This comment has been minimized.
This comment has been minimized.
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 18cf86fc5d
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 8f6c6a2438
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 9f5e827fcf
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
@codex review Please take another look. |
|
@codex review Please take another look. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: b6b0fdc2ba
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
@codex review Please take another look. |
|
Codex Review: Didn't find any major issues. 👍 ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
Summary
Wire the aggregated API server (
--app=aggregated-apiserver) to a real Coder deployment backend viacodersdk, replacing the hardcoded in-memory storage with codersdk-backed REST storage forCoderTemplateandCoderWorkspaceresources.Background
The aggregated API server mode previously served stub data from hardcoded maps. This PR makes it a real Kubernetes façade over a Coder deployment, so that Kubernetes clients can query and mutate Coder templates/workspaces using standard K8s REST semantics.
Key design decisions (v1):
<org>.<template-name>, Workspaces use<org>.<user>.<workspace-name>— the dot separator works because Coder names forbid dots while K8s object names allow them.--coder-session-tokenis used (no per-request impersonation in v1).CoderWorkspace.spec.runningdrives workspace transitions.Implementation
New packages
internal/aggregated/coder/— Backend helpers: SDK client factory (config.go), K8s↔Coder name parsing (names.go), codersdk→K8s error mapping (errors.go), andClientProviderinterface withStaticClientProvider(provider.go).internal/aggregated/convert/— Pure conversion functions betweencodersdk.Template/codersdk.Workspaceand the K8s aggregated API types.Modified packages
api/aggregation/v1alpha1/types.go— ExpandedCoderTemplate{Spec,Status}andCoderWorkspace{Spec,Status}with codersdk-aligned fields (organization, templateName, running, build status, etc.).internal/aggregated/storage/— Rewrotetemplate.goandworkspace.gofrom hardcoded maps to codersdk-backed CRUD:GET→TemplateByName,LIST→Templates,CREATE→CreateTemplate,DELETE→DeleteTemplateGET→WorkspaceByOwnerAndName,LIST→Workspaces,CREATE→CreateUserWorkspace+ optional stop build,UPDATE→ start/stop viaCreateWorkspaceBuild,DELETE→ delete transition buildinternal/app/apiserverapp/apiserverapp.go— AddedOptionsstruct andRunWithOptions, changedNewAPIGroupInfoto accept aClientProvider, updated OpenAPI definitions.app_dispatch.go— Added--coder-session-token,--coder-url,--coder-request-timeoutCLI flags wired intoRunWithOptions.Tests
storage_test.gowith httptest-backed Coder API mock covering all CRUD operations.integration_test.gothat bootstraps the full aggregated API server and verifies HTTP responses through the complete stack.Risks
Run(ctx): The zero-argRunfunction now wrapsRunWithOptionswith empty options, which will fail without Coder configuration. This is intentional — the real entrypoint is nowRunWithOptionsvia CLI dispatch.CoderTemplateSpec.RunningandCoderTemplateStatus.AutoShutdownare retained temporarily for compatibility with existing callers (e.g., MCP tools). These should be removed in a follow-up once callers are migrated.coderdtestis not vendored (massive dep tree). Storage tests use httptest mocks instead. True coderdtest integration is deferred.📋 Implementation Plan
Wire aggregated API server → Coder (
codersdk) backendContext / Why
coder-k8shas an aggregated API server mode (--app=aggregated-apiserver) that currently serves the API groupaggregation.coder.com/v1alpha1, but its storage is hardcoded in-memory. We want that API server to become a real “Kubernetes façade” over a running Coder deployment, backed bygithub.com/coder/coder/v2/codersdk, so Kubernetes clients can query and mutate Coder templates/workspaces (and eventually other Coder resources) using standard K8s REST semantics.A key dev/test requirement is that we can run
coderdtest+ envtest to exercise the end-to-end path:k8s client → kube-apiserver (envtest) → API aggregation proxy → coder-k8s aggregated-apiserver → codersdk → coderdtestGoals (first milestone)
codersdk-backed storage for:CoderTemplate(codertemplates)CoderWorkspace(coderworkspaces)GET,LIST,CREATE,DELETE(optionallyUPDATEmeta)GET,LIST,CREATE,UPDATE(drives start/stop),DELETE(drives delete transition)coderdtest.Non-goals (for first milestone)
WATCHsemantics / informer-grade semantics.Evidence (repo + upstream facts)
internal/app/apiserverapp/apiserverapp.go(NewAPIGroupInforegisterscoderworkspacesandcodertemplates).rest.Getter+rest.Lister:internal/aggregated/storage/workspace.gointernal/aggregated/storage/template.goapi/aggregation/v1alpha1/types.gocodersdkis already vendored and in use:internal/coderbootstrap/client.goimportsgithub.com/coder/coder/v2/codersdkgo.modrequiresgithub.com/coder/coder/v2 v2.30.0codersdkmethods/signatures (vendored):(*codersdk.Client).Workspaces(ctx, codersdk.WorkspaceFilter) (codersdk.WorkspacesResponse, error)invendor/.../codersdk/workspaces.go(*codersdk.Client).WorkspaceByOwnerAndName(ctx, owner, name string, params codersdk.WorkspaceOptions) (codersdk.Workspace, error)invendor/.../codersdk/workspaces.go(*codersdk.Client).CreateUserWorkspace(ctx, user string, req codersdk.CreateWorkspaceRequest) (codersdk.Workspace, error)invendor/.../codersdk/organizations.go(*codersdk.Client).CreateWorkspaceBuild(ctx, workspaceID uuid.UUID, req codersdk.CreateWorkspaceBuildRequest) (codersdk.WorkspaceBuild, error)invendor/.../codersdk/workspaces.go(*codersdk.Client).TemplatesByOrganization(ctx, orgID uuid.UUID) ([]codersdk.Template, error)invendor/.../codersdk/organizations.go(*codersdk.Client).TemplateByName(ctx, orgID uuid.UUID, name string) (codersdk.Template, error)invendor/.../codersdk/organizations.go(*codersdk.Client).CreateTemplate(ctx, orgID uuid.UUID, req codersdk.CreateTemplateRequest) (codersdk.Template, error)invendor/.../codersdk/organizations.go(*codersdk.Client).DeleteTemplate(ctx, templateID uuid.UUID) errorinvendor/.../codersdk/templates.gocoderdtestexists upstream and provides in-memory coderd + helpers:coderd/coderdtestfromcoder/codertagv2.30.0(confirmed viaweb_fetch).vendor/k8s.io/apimachinery/pkg/util/validation/validation.go(dns1123SubdomainFmt).Chosen semantics (to keep v1 implementable)
These are explicit “v1” choices to avoid design deadlocks.
Resources remain namespace-scoped; namespace == CoderControlPlane namespace
CoderTemplateandCoderWorkspaceremain namespaced in the aggregated API (NamespaceScoped() == true).metadata.namespaceis interpreted as the namespace that contains the backingcoder.com/v1alpha1 CoderControlPlaneinstance.genericapirequest.NamespaceValue(ctx)to select the control plane and build acodersdk.Clientfor that namespace.Namespace is NOT the Coder organization
metadata.name, and optionally mirrored inspec.organization).Deterministic name formats (encode Coder lookup keys)
<org>.<template-name><org>.<user>.<workspace-name>.as the separator because Coder “names” are alphanumeric-with-hyphens (no dots) (codersdk.NameValid), while Kubernetes object names allow dots.Admin token, but no default owner
--coder-session-token(admin token in dev/test); we do not implement per-request user impersonation in v1.me; it derives the target user from the workspace object name and can list across all workspaces visible to the admin token.CoderWorkspace.spec.runningdrives start/stopUPDATEcompares desired.spec.runningto current state and performsCreateWorkspaceBuildwithtransition=start|stop.Why these semantics?
GETrequests provide only{namespace, name}. Using namespace to pick the backing CoderControlPlane, and encoding{org,user}into the object name, makesGETdeterministic without extra indices/caches.Implementation Plan (swarm-friendly)
0) Repo-wide prep / guardrails
Owner: 1 agent
assertion failed:style ininternal/aggregated/storage/*).1) API type expansion (if we want meaningful CREATE)
Owner: 1–2 agents
1.1 Update
api/aggregation/v1alpha1/types.goAdd fields needed to create/drive resources.
Resource scope:
// +kubebuilder:resource:scope=Namespacedmarkers is optional but recommended for clarity.Suggested shapes (keep minimal; everything optional except create-critical fields):
1.2 Regenerate generated artifacts
Must do in Exec mode after type changes:
make codegenmake manifestsmake docs-reference(if API reference docs exist/are required)1.3 Update OpenAPI definitions used by aggregated-apiserver
internal/app/apiserverapp/apiserverapp.gocurrently manually builds a very small OpenAPI schema for spec/status. Update it to reflect the new fields (or, if we decide to defer schema accuracy, at minimum ensure required create fields are present in the schema).2) Add a codersdk-backed “backend” helper package
Owner: 1 agent
Create
internal/aggregated/coder/(new package) with the shared plumbing that storage needs.2.1 Options / factory
File:
internal/aggregated/coder/config.goNotes:
*codersdk.Client’s token at runtime; treat it as immutable.2.2 K8s name parsing (org/user keys)
File:
internal/aggregated/coder/names.goOrg resolution uses the parsed
org(and/orspec.organization) and calls:orgObj, err := sdk.OrganizationByName(ctx, orgName)2.3 Error mapping
File:
internal/aggregated/coder/errors.go*codersdk.Error(status codes) into Kubernetes-style errors:apierrors.NewNotFoundapierrors.NewForbiddenapierrors.NewAlreadyExists/apierrors.NewConflict(choose based on operation)apierrors.NewInternalErrorAlso: wrap unknown errors with context and retain the original error for logs.
2.4 Resolve
CoderControlPlaneper request namespace (namespace → Coder URL)File:
internal/aggregated/coder/provider.go(or similar)We need a single place that translates the Kubernetes request namespace (which is the control plane namespace) into a
*codersdk.Clientconfigured for that control plane.Suggested interface:
Suggested concrete implementation (names are illustrative):
Algorithm for
ClientForNamespace:namespace != "".CoderControlPlanein that namespace:ControlPlaneName != "":Get(namespace, ControlPlaneName).ListCoderControlPlanes in namespace and require exactly 1.controlPlane.Status.URLand parse as*url.URL.DefaultCoderURL != nil, use fallback.ServiceUnavailable/BadRequest).codersdk.New(url)and set the fixed session token + HTTP timeout.RBAC impact (cluster): aggregated-apiserver service account must be able to
get/list/watchcodercontrolplanesacross namespaces.3) Implement conversion layer (codersdk ↔ aggregated API types)
Owner: 1–2 agents
Create
internal/aggregated/convert/with pure conversion functions.3.1 Templates
File:
internal/aggregated/convert/template.go3.2 Workspaces
File:
internal/aggregated/convert/workspace.goAdd unit tests for converters (no network).
4) Replace hardcoded storage with codersdk-backed storage
Owner: 2–3 agents (templates vs workspaces can be parallel)
4.1 Shared storage base patterns
Keep the defensive style and interface assertions.
Add new fields to both storages:
provider coder.ClientProvider(resolves*codersdk.Clientfrom the request namespace / control plane)Update constructors to accept dependencies:
storage.NewWorkspaceStorage(provider coder.ClientProvider) *WorkspaceStoragestorage.NewTemplateStorage(provider coder.ClientProvider) *TemplateStorageNamespace semantics:
NamespaceScoped() == true.genericapirequest.NamespaceValue(ctx)as the CoderControlPlane namespace.namespace != ""for all operations (includingList). If the request namespace is empty (cluster-wide list), return a clearBadRequestexplaining that-A/--all-namespacesis not supported yet.CoderControlPlaneobjects cluster-wide and aggregating results.4.2 TemplateStorage: implement CRUD
File:
internal/aggregated/storage/template.goImplement interfaces:
rest.Getterrest.Listerrest.Createrrest.GracefulDeleterrest.Updaterfor meta updates usingUpdateTemplateMetaConcrete mapping (namespaced; namespace == control plane namespace; name format
"<org>.<template-name>"):In every method, resolve the backend client using the request namespace:
ns := genericapirequest.NamespaceValue(ctx)sdk, err := provider.ClientForNamespace(ctx, ns)List
ns == "", returnBadRequest(v1 does not support-A/--all-namespaces).tpls, err := sdk.Templates(ctx, codersdk.TemplateFilter{}).metadata.namespace = nsmetadata.name = coder.BuildTemplateName(t.OrganizationName, t.Name)spec.organization = t.OrganizationNameGet
{name}usingcoder.ParseTemplateName→(orgName, templateName).org, err := sdk.OrganizationByName(ctx, orgName).tpl, err := sdk.TemplateByName(ctx, org.ID, templateName).convert.TemplateToK8s(ns, tpl).Create
obj.Name→(orgName, templateName).obj.Spec.Organization(if set) matchesorgName(else set it).org, err := sdk.OrganizationByName(ctx, orgName).codersdk.CreateTemplateRequestfrom spec + parsed templateName, thencreated, err := sdk.CreateTemplate(ctx, org.ID, req).convert.TemplateToK8s(ns, created).Delete
{name}→(orgName, templateName).sdk.DeleteTemplate(ctx, tpl.ID).&metav1.Status{Status: "Success"}(or the deleted object).4.3 WorkspaceStorage: implement CRUD + start/stop via Update
File:
internal/aggregated/storage/workspace.goImplement interfaces:
rest.Getterrest.Listerrest.Createrrest.Updaterrest.GracefulDeleterConcrete mapping (namespaced; namespace == control plane namespace; name format
"<org>.<user>.<workspace-name>"):In every method, resolve the backend client using the request namespace:
ns := genericapirequest.NamespaceValue(ctx)sdk, err := provider.ClientForNamespace(ctx, ns)List
ns == "", returnBadRequest(v1 does not support-A/--all-namespaces).wres, err := sdk.Workspaces(ctx, codersdk.WorkspaceFilter{}).metadata.namespace = nsmetadata.name = coder.BuildWorkspaceName(w.OrganizationName, w.OwnerName, w.Name)spec.organization = w.OrganizationNameGet
{name}usingcoder.ParseWorkspaceName→(orgName, userName, workspaceName).ws, err := sdk.WorkspaceByOwnerAndName(ctx, userName, workspaceName, codersdk.WorkspaceOptions{}).ws.OrganizationName != orgName, return NotFound.convert.WorkspaceToK8s(ns, ws).Create
obj.Name→(orgName, userName, workspaceName).obj.Spec.Organization(if set) matchesorgName(else set it).obj.Spec.TemplateName.org, err := sdk.OrganizationByName(ctx, orgName).tpl, err := sdk.TemplateByName(ctx, org.ID, obj.Spec.TemplateName).codersdk.CreateWorkspaceRequest:Name = workspaceNameTemplateID = tpl.ID(orTemplateVersionIDif provided)created, err := sdk.CreateUserWorkspace(ctx, userName, req)..spec.running == false, immediately queue a stop build:sdk.CreateWorkspaceBuild(ctx, created.ID, codersdk.CreateWorkspaceBuildRequest{Transition: stop, TemplateVersionID: created.LatestBuild.TemplateVersionID})convert.WorkspaceToK8s(ns, created).Update (key behavior)
{name}→(orgName, userName, workspaceName)and fetch current workspace by owner+name.objInfo.UpdatedObject(ctx, oldObj).metadata.name,spec.organization,spec.templateName).runningchanges, queue build transition start/stop.UpdateWorkspaceTTL/UpdateWorkspaceAutostart.Delete
{name}→(orgName, userName, workspaceName); fetch workspace; if org mismatch return NotFound; queue build:sdk.CreateWorkspaceBuild(ctx, ws.ID, codersdk.CreateWorkspaceBuildRequest{Transition: delete, TemplateVersionID: ws.LatestBuild.TemplateVersionID})&metav1.Status{Status: "Success"}(or the deleted object).4.4 Defensive programming requirements
assertion failed:on nil storage, nil ctx, missing required spec fields.<org>.<template-name>,<org>.<user>.<workspace-name>) and required spec fields early with clear error messages.4.5 Update existing tests impacted by the refactor
internal/aggregated/storage/storage_test.go(it currently assumes hardcoded maps and zero-arg constructors).coderdtestintegration tests described in §7.2.httptest.Serverthat implements only the required/api/v2/...endpoints, similar tointernal/coderbootstrap/client_test.go.internal/app/apiserverapp/apiserverapp_test.goto pass a dummycoder.ClientProviderintoNewAPIGroupInfoonce its signature changes.5) Wire storage dependencies into aggregated-apiserver bootstrap
Owner: 1 agent
5.1 Add apiserver options
File:
internal/app/apiserverapp/apiserverapp.goRun(ctx)can remain as a thin wrapper that reads env vars for backwards compatibility, or we can update callsites to always useRunWithOptions.5.2
NewAPIGroupInfomust accept storage depsChange signature to accept a
coder.ClientProvider(or a small dep struct containing it), and update:internal/app/apiserverapp/apiserverapp_test.goExample:
6) CLI flag plumbing
Owner: 1 agent
File:
app_dispatch.goAdd flags to the existing
FlagSet(define them regardless of app mode; only enforce for aggregated-apiserver):--coder-session-token(admin token for the backing coderd)--coder-request-timeout(default30s)--coder-url(optional fallback when the namespace’s CoderControlPlane has nostatus.url)--coder-control-plane-name(optional selector when multiple control planes exist in a namespace)Also document/validate the required object name formats:
<org>.<template-name><org>.<user>.<workspace-name>Update the aggregated-apiserver dispatch to call
apiserverapp.RunWithOptions(ctx, opts).Update tests in
main_test.goif signature changes (the dispatch tests currently monkeypatchrunAggregatedAPIServerApp func(context.Context) error).7) Testing strategy
7.1 Unit tests (fast)
Owner: 1 agent
internal/aggregated/convert/*_test.go:internal/aggregated/coder/errors_test.go:codersdk.NewTestError→apierrors.IsNotFound/IsForbidden/....7.2 Storage integration tests with
coderdtest(primary correctness signal)Owner: 1–2 agents
Add tests in
internal/aggregated/storage/*_integration_test.go(or similar) that:(Imports you will need once you add these tests:
github.com/coder/coder/v2/coderd/coderdtestand likelygithub.com/coder/coder/v2/provisioner/echo.Adding these will expand
vendor/aftermake vendor/make verify-vendor.)client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})coderdtest.CreateFirstUser(t, client)to authenticateclient.spec.organization:org, err := client.OrganizationByName(ctx, "default")(orCreateOrganizationif needed).version := coderdtest.CreateTemplateVersion(t, client, orgID, &echo.Responses{...})coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)template := coderdtest.CreateTemplate(t, client, orgID, version.ID)coder.ClientProviderthat returns the authenticatedclientfor a chosen control-plane namespace (e.g.coder-123)."<org>.<template-name>""<org>.<user>.<workspace-name>"Create/Updateincludemetadata.namespace = <control-plane-namespace>and required spec fields (spec.organization,spec.templateName, etc.).Create→Get→List→DeleteCreate→Get→List→Update(toggle running) →DeleteEnsure contexts use a namespaced request context, e.g.
genericapirequest.WithNamespace(context.Background(), "coder-123").7.3 End-to-end: envtest + APIService proxy (stretch but matches requirement)
Owner: 1–2 agents
Goal: route requests through envtest kube-apiserver’s aggregation layer.
Implementation suggestion:
internal/app/apiserverapp/e2e_test.goorinternal/aggregated/e2e/) so it can spin up its own envtest environment without inheriting the controller suite’s CRD installation.envtest.Environment{}withCRDDirectoryPathsempty (or pointing to a directory that excludes theaggregation.coder.com_*CRDs).Steps:
aggregation.coder.comCRDs (otherwise CRDs “own” the group and proxying won’t work).apiserverappon a dynamic port (listener127.0.0.1:0).coder-k8s-system(or similar)coder-k8s-aggregated-apiserverin that namespace127.0.0.1:<port>apiregistration.k8s.io/v1 APIServicenamedv1alpha1.aggregation.coder.comwith:spec.servicepointing to that servicespec.insecureSkipTLSVerify: true(test-only)rest.Configto call:POST /apis/aggregation.coder.com/v1alpha1/namespaces/<control-plane-namespace>/coderworkspacesmetadata.name: "<org>.<user>.<workspace-name>"andspec.organization: "<org>"Implementation detail: gate this test with a build tag or
t.Skipif the envtest kube-apiserver does not support aggregation in this environment.8) Validation / CI readiness
Owner: 1 agent (final pass)
Run, in order:
make verify-vendor(especially after addingcoderdtesttest deps)make testmake buildmake lintIf API structs changed:
make codegenmake manifestsAgent task breakdown (parallel execution)
deploy/manifests + RBAC).Generated with
mux• Model:anthropic:claude-opus-4-6• Thinking:xhigh• Cost:$0.86